﻿#include "Automate.h"
#include "Etat.h"
#include "Etiquette.h"
#include "ListeTransitions.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <cctype>
#include <map>
#include <set>
#include <queue>

using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::stringstream;
using std::map;

Automate::Automate()
{

}

Automate::Automate(string nomFichier)
{
	ifstream fichier;
	try{
		fichier.open(nomFichier);
		if (fichier.fail()) {
			throw std::invalid_argument(nomFichier + ": Mauvais nom de fichier pour l'automate");
		}

		//On lit la premiere ligne
		unsigned nbrEtats;
		fichier >> nbrEtats;
		if (fichier.fail()) {
			throw std::exception(string(nomFichier + ": La premiere ligne n'est pas un nombre").c_str());
		}

		while (!fichier.eof())
		{
			string commande;
			//On récupère une ligne
			getline(fichier, commande);

			bool initial = false;
			bool initialExistant = false;
			bool terminal = false;
			Etat* etatSource = nullptr;
			Etat* etatDestination = nullptr;
			Etiquette etiquette;
			//On analyze la ligne caractère par caractère
			for (unsigned i = 0; i < commande.size(); ++i)
			{
				char currentChar = commande[i];

				if (currentChar == ' ')
					continue;

				if (currentChar == 'I') {
					if(!initialExistant)
					{
						initial = initialExistant = true;
						continue;
					}
					else
					{
						throw std::exception("Plusieurs etats initiaux");
					}
				}

				if (currentChar == 'T') {
					terminal = true;
					continue;
				}

				//Si la commande actuelle est un nombre
				if (isdigit(currentChar))
				{
					//On convertit le caractere courrant en nombre
					int tempID = currentChar - '0';
					//Si le premier état n'a pas encore été défini
					if (etatSource == nullptr)
					{
						etatSource = liste.searchEtat(tempID);
						//Si l'état n'existe pas, on le créé
						if (etatSource == nullptr) {
							etatSource = new Etat(tempID, initial, terminal);
							liste.addEtat(etatSource);
							initial = false;
							terminal = false;
						}
					}
					else //Dans le cas où c'est le deuxieme état
					{
						etatDestination = liste.searchEtat(tempID);
						//Si l'état n'existe pas, on le créé
						if (etatDestination == nullptr) {
							etatDestination = new Etat(tempID, initial, terminal);
							liste.addEtat(new Etat(tempID, initial, terminal));
							initial = false;
							terminal = false;
						}
					}
					continue;
				}

				//Si c'est un caractere
				if (isalpha(currentChar)) {
					etiquette.addSymbole(currentChar);
				}
			} //Fin du for

			//Si on a une liaison à créer
			if (etiquette.size() > 0)
			{
				if (etatSource == nullptr) //Si on oublié la source...
					throw std::exception(string(nomFichier + ": La transition " + etiquette.getSymboles() + "n'a pas de source").c_str());
				if (etatDestination == nullptr) //Si on oublié la destination...
					throw std::exception(string(nomFichier + ": La transition " + etiquette.getSymboles() + " n'a pas de destination").c_str());
				etatSource->ajouterTransition(etiquette, etatDestination);
			}
		}

		if (nbrEtats != liste.size())
			throw std::exception("Pas assez/trop d'etats dans le fichier");
		if (getEtatInitial() == nullptr)
			throw std::exception("Aucuns etats initiaux");

		fichier.close();
	}
	catch (...)
	{
		fichier.close();
		throw;
	}
}

Automate::~Automate()
{
	liste.purgerEtats();
}

Etat* Automate::getEtat(unsigned id) const
{
	return liste.searchEtat(id);
}

void Automate::ajouterEtat(unsigned index, bool initial /*= false*/, bool terminal /*= false*/)
{
	if (initial && getEtatInitial() != nullptr)
	{
		std::cerr << "Etat initial deja existant, l'etat " << index << " n'a pas pu etre ajoute";
		return;
	}
	if (liste.searchEtat(index) == nullptr)
		liste.addEtat(new Etat(index,initial,terminal));
}

void Automate::genererFichierAutomate(string fichierNom)
{
	ofstream fichier;
	try {
		fichier.open(fichierNom,std::ios::trunc);
		if (fichier.fail())
			throw std::exception(string("Une erreure inconnue s'est produite lors de la creation du fichier").c_str());

		stringstream donnees;
		string ligneEcrire;

		//Ligne du nombre d'états
		donnees << this->liste.size() << endl;

		
		//On insere les etats
		for (unsigned i = 0; i < liste.size(); ++i)
		{
			if (liste[i]->estInitial())
				donnees << "I ";
			if (liste[i]->estTerminal())
				donnees << "T ";
			donnees << liste[i]->getId();
			donnees << endl;
		}

		//On insere les transitions
		//Pour tous les etats
		for (unsigned i = 0; i < liste.size(); ++i)
		{
			//On recupere leurs transitions
			const ListeTransitions& listeTrans = liste[i]->getTransitions();
			for (unsigned j = 0; j < listeTrans.size(); ++j)
			{
				Transition* tempTrans = listeTrans[j];
				unsigned sourceId = tempTrans->getSource()->getId();
				donnees << sourceId << " ";

				//On inseres toutes les etiquettes de la transition
				donnees << tempTrans->getEtiquette().getSymboles() << " ";
				

				unsigned destId = tempTrans->getDestination()->getId();
				donnees << destId;

				donnees << endl;
			}
		}
		

		fichier << donnees.rdbuf();
		fichier.close();

	}
	catch (...)
	{
		fichier.close();
		throw;
	}
}

bool Automate::estDeterministe()
{
	//Pour tous les etats
	for (unsigned i = 0; i < liste.size(); ++i)
	{
		Etat* etatCourant = liste[i];
		const ListeTransitions& transitions = etatCourant->getTransitions();
		map<char, bool> symboleTrouve;
		for (const_transItr it = transitions.begin(); it != transitions.end(); ++it)
		{
			string symboles = (*it)->getEtiquette().getSymboles();
			for (string::iterator it2 = symboles.begin(); it2 != symboles.end(); ++it2)
			{
				char tempChar = *it2; //char actuel
				//Si le symbole a déjà été trouvé
				if (symboleTrouve[tempChar])
					return false;
				else
					symboleTrouve[tempChar] = true;
			}
		}
	}
	return true;
}

Automate Automate::determiniser()
{
	ListeEtats etatTemp;
	Automate autoTemp;
	std::set<char> etique;
	std::queue<int> queueNbEtat;
	Etiquette etiquetteTemp;
	int nbEtat = 0;
	int verif;
	int idTemp = 0;

	bool innitial = liste.getEtat(0)->estInitial();
	bool terminal = liste.getEtat(0)->estTerminal();

	queueNbEtat.push(1);

	Etat* etat = new Etat(0, innitial, terminal);

	etatTemp.addEtat(etat);

	while(etatTemp.size() !=0){
		verif = queueNbEtat.front();
		innitial = false;
		terminal = false;

		for (int i = 0; i < verif; i++){
			if(liste.getEtat(i)->estInitial()){
				innitial = true;
			}
			if(liste.getEtat(i)->estTerminal()){
			terminal = true;
			}
		}
		
			
		autoTemp.ajouterEtat(idTemp, innitial, terminal);
		verif = queueNbEtat.front();
		queueNbEtat.pop();
		
		for(int j = 0; j <verif; j++){
			etat = etatTemp.getEtat(0);
			for(const_transItr it = etatTemp.getEtat(0)->getTransitions().begin(); it != etatTemp.getEtat(0)->getTransitions().end(); it++){
				for(int i = 0; i <(*it)->getEtiquette().getSymboles().length(); i++){
					if((*it)->getEtiquette().getSymboles()[i]>'a' && (*it)->getEtiquette().getSymboles()[i]< 'z'){
						etique.insert((*it)->getEtiquette().getSymboles()[i]);
					}
				}
			}
			std::set<char>::const_iterator 
			sit (etique.begin()), 
			send(etique.end()); 
			for(;sit!=send;++sit){
				for(const_transItr it = etatTemp.getEtat(0)->getTransitions().begin(); it != etatTemp.getEtat(0)->getTransitions().end(); it++){
					if((*it)->getEtiquette().contientSymbole((*sit))){
						nbEtat++;
						etatTemp.addEtat((*it)->getDestination());
					}
				}
				queueNbEtat.push(nbEtat);
			}
			idTemp++;
			etatTemp.retirerEtat(0);	
		}
	}
	return autoTemp;
}


bool Automate::reconnaitreMot(string mot)
{
	if (!estDeterministe())
		return false;

	Etat* etatCourant = getEtatInitial();
	if (etatCourant == nullptr)
		throw std::exception(string("L'automate n'a pas d'etat initial").c_str());

	for (unsigned i = 0; i < mot.size(); ++i)
	{
		//symbole est le caractère courrant
		char symbole = mot[i];
		if (isalpha(symbole))
		{
			ListeEtats etatSuivant = etatCourant->cible(symbole);
			//S'il n'y a pas d'état suivant
			if (etatSuivant.size() == 0)
				return false;
			if (etatSuivant.size() > 1)
				throw std::exception(string("estDeterministe() a laisse passer un automate non deterministe").c_str());

			//Si il n'y qu'un et un seul état suivant...
			etatCourant = etatSuivant[0];
		}
	}
	if (etatCourant->estTerminal())
		return true;

	return false;
}

void Automate::print()
{
	cout << "Automate: " << endl;
	cout << "Etats: ";
	for (unsigned i = 0; i < this->liste.size(); )
	{
		Etat* etat = liste[i];
		if (etat->estInitial())
			cout << "(I)";
		if (etat->estTerminal())
			cout << "(T)";
		cout << etat->getId();
		cout << "[" << etat->listerEtiquettes().getSymboles() << "]";
		if(++i != liste.size())
			cout << ", ";
	}
	cout << endl;
}

Etat* Automate::getEtatInitial() const
{
	for (unsigned i = 0; i < liste.size(); ++i)
	{
		if (liste[i]->estInitial())
			return liste[i];
	}
	return nullptr;
}

void Automate::retirerEtat(unsigned id)
{
	liste.retirerEtat(id);

	for (unsigned i = 0; i < liste.size(); ++i)
		liste[i]->retirerTransition(id);
}

